/**
 * @file sxml.c
 * @brief XML generator, using template.
 * @author Hongbao Chen
 * @version 1.0 Beta
 * @date 2012-04-12
 */
#ifndef SXML_H_
#define SXML_H_

#ifdef __cplusplus
extern "C" {
#endif


#ifndef SSTRING_H_
#include "sstring.h"
#endif
#ifndef SUTIL_H_
#include "sutil.h"
#endif

#define FRAC_TYP_TAG	1
#define FRAC_TYP_TXT	2

#define FRAC_STA_RAW	3
#define FRAC_STA_REAL	4


/**
 * @brief Structure to hold the XML fraction, in the form of string. 
 */
typedef struct XmlFraction {
	int type;			/** type of the faction(tag, normal xml text)*/
	int status;			/** the status of the fraction(raw, unprocessed, and real, has been replaced with value)*/
	struct sstring		_name;	/** If the fraction is a tag, holding the tag name.*/
	struct sstring		_str;	/** Hold the contents of the fraction including xml text and tag value.*/
	struct XmlFraction	*pnext;
	struct XmlFraction	*pprev;
} XmlFrac;


/**
 * @brief Holder of XML document, in the form of string.
 */
struct XML {
	struct sstring		xml_string;
};

#define pStr		_str.phead
#define pName		_name.phead


#define SPLITER		"@"
#define FRAC_TAG_LEN	32
#define FRAC_TXT_LEN	512


/**
 * @brief Method to pick a place holder in XML template.
 * Receive the pointer to the first '@' in a tag, and return a pointer to the last
 * '@' in the same tag. Store the tag value into the _pBuf.
 * If it meets the '\0', denoting the end of a string, it will return the pointer to
 * the previous position to the '\0', that is 'pointer_to_end -1'.
 *
 * @param _pTag The pointer to the tag, pointing at '@'.
 * @param _pBuf Buffer used to hold the tag.
 * 
 * @return The pointer to the last '@' in the tag. 
 */
char * pickTag(char *_pTag, String *_pBuf) {

	int at = 0;
	while( *_pTag != '\0') {

		if(*_pTag == '@') {
			if( at == 1) {
				sstrAdd( _pBuf, '\0');
				return _pTag;
				
			} else {
				at = 1;
			}
		}
		else {
			sstrAdd( _pBuf, *_pTag);
		}
		_pTag++;
	}

	sstrAdd( _pBuf, '\0');
	return _pTag-1;
}


/**
 * @brief Receive the pointer to a string, which is not at '@' but following it adjacently.
 * It will return the pointer to the last element other than the '@', preceding it adjacently.
 *
 * @param _pTxt Pointer to the string to be processed.
 * @param _pBuf Buffer to hold the the contents.
 *
 * @return The pointer to the last element in contents other than '@'.
 */
char * pickTxt(char *_pTxt, String *_pBuf) {
	
	while( *_pTxt != '\0') {
		if(*_pTxt == '@') {
			sstrAdd( _pBuf, '\0');
			return _pTxt-1;
		} else {
			sstrAdd( _pBuf, *_pTxt);
		}
		_pTxt++;
	}

	sstrAdd( _pBuf, '\0');
	return _pTxt-1;
}

/**
 * @brief Free fraction structure.
 *
 * @param _pXmlFrac Pointer to the head of the fraction list.
 */
void freeXmlFraction(struct XmlFraction *_pXmlFrac) {
	if(_pXmlFrac == NULL)
		return;
	struct XmlFraction *pFrac = NULL;
	for( ; _pXmlFrac != NULL; _pXmlFrac=_pXmlFrac->pnext) {
		if(pFrac != NULL)
			free(pFrac);
		sstrFree( &_pXmlFrac->_name);
		sstrFree( &_pXmlFrac->_str);
		pFrac = _pXmlFrac;
	}

	free(pFrac);
}

/**
 * @brief Build the list of xml fraction from the xml template text.
 *
 * @param _pXmlStr XML template text.
 *
 * @return The head node of the list.
 */
struct XmlFraction* buildXmlFraction(const char *_pXmlStr, struct XmlFraction *_pXmlFrac) {
	
	struct XmlFraction *pXmlFrac = _pXmlFrac;
	struct XmlFraction *pXmlCurr = NULL;
	char *pXmlStr = (char *)_pXmlStr;

	String str = SSTR_INIT;
	while( *pXmlStr != '\0') {
		switch(*pXmlStr) {
			case '@':
				pXmlStr = pickTag(pXmlStr, &str);
				if( pXmlFrac == NULL && pXmlCurr == NULL) {
					pXmlFrac = (struct XmlFraction *)MALLOC(sizeof(struct XmlFraction));
					bzero(pXmlFrac, sizeof(struct XmlFraction));

					if(pXmlFrac == NULL) {
						return NULL;
					}
					pXmlCurr = pXmlFrac;
					pXmlCurr->pprev = NULL;

				} else if(pXmlFrac != NULL && pXmlCurr == NULL) {
					pXmlCurr =  pXmlFrac;
					pXmlCurr->pprev = NULL;
				}
				else {
					pXmlCurr->pnext = (struct XmlFration *)MALLOC(sizeof(struct XmlFraction));
					bzero(pXmlCurr->pnext, sizeof(struct XmlFraction));

					if(pXmlCurr->pnext == NULL) {
						freeXmlFraction(pXmlFrac);
						return NULL;
					}
					pXmlCurr->pnext->pprev = pXmlCurr;
					pXmlCurr = pXmlCurr->pnext;
				}

				sstrCat( &(pXmlCurr->_name), str.phead);
				pXmlCurr->status = FRAC_STA_RAW;
				pXmlCurr->type	= FRAC_TYP_TAG;
				pXmlCurr->pnext = NULL;

				sstrClear(&str);
				break;
			default:
				pXmlStr = pickTxt(pXmlStr, &str);
				if( pXmlFrac == NULL && pXmlCurr == NULL) {
					pXmlFrac = (struct XmlFraction *)MALLOC(sizeof(struct XmlFraction));
					bzero(pXmlFrac, sizeof(struct XmlFraction));

					if(pXmlFrac == NULL)
						return NULL;
					pXmlCurr = pXmlFrac;
					pXmlCurr->pprev = NULL;

				} else if( pXmlFrac != NULL && pXmlCurr ==NULL){
					pXmlCurr = pXmlFrac;
					pXmlCurr->pprev = NULL;

				} else{
					pXmlCurr->pnext = (struct XmlFration *)MALLOC(sizeof(struct XmlFraction));
					bzero(pXmlCurr->pnext, sizeof(struct XmlFraction));

					if(pXmlCurr->pnext == NULL) {
						freeXmlFraction(pXmlFrac);
						return NULL;
					}
					pXmlCurr->pnext->pprev = pXmlCurr;
					pXmlCurr = pXmlCurr->pnext;
				}

				sstrCat( &(pXmlCurr->_str), str.phead);
				pXmlCurr->status = FRAC_STA_RAW;
				pXmlCurr->type	= FRAC_TYP_TXT;
				pXmlCurr->pnext = NULL;

				sstrClear(&str);
				break;
		}

		++pXmlStr;
	}
	return pXmlFrac;
}


/**
 * @brief Find the first fraction that has the same tag name with _pFracName.
 *
 * @param _pFracName The name of the fraction to be searched.
 * @param _pXmlFrac The head of the fraction list.
 *
 * @return The designated fraction to search.
 */
struct XmlFraction* selectFirstFraction(const char *_pFracName, struct XmlFraction *_pXmlFrac) {

	struct XmlFraction *pXmlFrac = _pXmlFrac;
	for(; pXmlFrac!=NULL; pXmlFrac=pXmlFrac->pnext) {
		if(pXmlFrac->type == FRAC_TYP_TAG && strcmp(pXmlFrac->pName, _pFracName) == 0) {
			return pXmlFrac;
		}
	}

	return NULL;
}


/**
 * @brief Copy the value to the fraction(_str) and set status to indicate it's mature.
 *
 * @param _pNewVal the value to be set.
 * @param _pXmlFrac the fraction to be matured.
 *
 * @return the same fraction as _pXmlFrac.
 */
struct XmlFraction* replaceXmlFraction(const char *_pNewVal, struct XmlFraction *_pXmlFrac) {

	sstrClear( &(_pXmlFrac->_str));
	sstrCat( &(_pXmlFrac->_str), _pNewVal);
	_pXmlFrac->status = FRAC_STA_REAL;
	return _pXmlFrac;
}

/**
 * @brief Find the last fraction with the same tag name as _pFracName.
 * [note] refer to selectFirstFraction().
 */
struct XmlFraction* selectLastFraction( const char *_pFracName, struct XmlFraction *_pXmlFrac) {

	struct XmlFraction *pXmlFrac = _pXmlFrac;
	struct XmlFraction *pCurr = NULL;
	int got = 0;
	for(; pXmlFrac != NULL; pXmlFrac=pXmlFrac->pnext) {
		if(pXmlFrac->type == FRAC_TYP_TAG) {
			if(got == 0) {
				if(strcmp( pXmlFrac->pName, _pFracName) == 0) {
					got = 1;
					pCurr = pXmlFrac;
				}
			} else {
				if(strcmp( pXmlFrac->pName, _pFracName) == 0) {
					pCurr = pXmlFrac;
				} else {
					break;
				}
			}
		}
	}
	return pCurr;
}


/**
 * @brief Clone the fraction 'deeply'.
 *
 * @param _pSrc The source fraction to be cloned.
 *
 * @return The cloned fraction.
 */
struct XmlFraction* cloneXmlFraction( struct XmlFraction *_pSrc) {
	struct XmlFraction* pXmlFrac = (struct XmlFraction *)MALLOC(sizeof(struct XmlFraction));
	if(pXmlFrac == NULL)
		return NULL;
	bzero(pXmlFrac, sizeof(struct XmlFraction));

	/** Only when both copies fail, the clone fail.*/
	if(sstrClone2(&(pXmlFrac->_name), &( _pSrc->_name)) == NULL &&
			sstrClone2(&(pXmlFrac->_str), &( _pSrc->_str)) == NULL)
		return NULL;

	pXmlFrac->type =  _pSrc->type;
	pXmlFrac->status = _pSrc->status;

	pXmlFrac->pnext = NULL;

	pXmlFrac->pprev = NULL;

	return pXmlFrac;
}


/**
 * @brief Thie method actually, insert a fraction node into the list, denoted by _pXmlFrac as the head.
 * The inserted fraction is mature, which has received the value and status has been changed to real.
 *
 * @param _pTag The tag to turn mature.
 * @param _pVal The value to be set.
 * @param _pXmlFrac The head to the xml fraction list.
 *
 * @return 0 for success, error unspecified.
 */
int setXmlValue( const char *_pTag, const char *_pVal, struct XmlFraction *_pXmlFrac) {

	struct XmlFraction *pXmlFrac = selectFirstFraction( _pTag, _pXmlFrac);
	if(pXmlFrac == NULL) {
		return (int)-1;
	}
	struct XmlFraction *pCloneFrac = cloneXmlFraction(pXmlFrac);
	replaceXmlFraction( _pVal, pCloneFrac);
	
	pXmlFrac = selectLastFraction( _pTag, _pXmlFrac);
	if(pXmlFrac == NULL) {
		return (int)-1;
	}
	pCloneFrac->pnext = pXmlFrac->pnext;
	pCloneFrac->pprev = pXmlFrac;

	pXmlFrac->pnext = pCloneFrac;

	pCloneFrac->pnext->pprev = pCloneFrac;
	return (int)0;

}



/**
 * @brief Put all fractions together and produce a standard XML string.
 *
 * @param _pXml The strut XML pointer, to hold the xml output.
 * @param _pXmlFrac The fraction list to be processed.
 *
 * @return If _pXml is NULL, it will malloc a struct and return its pointer. If _pXml is not NULL,
 * it will return _pXml.
 */
struct XML* realizeXmlFraction(struct XML *_pXml, struct XmlFraction *_pXmlFrac) {

	if( _pXmlFrac == NULL)
		return NULL;
	if( _pXml == NULL) {
		_pXml = (struct XML *)MALLOC(sizeof(struct XML));
	}

	sstrClear(&(_pXml->xml_string));

	for( ;_pXmlFrac!=NULL; _pXmlFrac=_pXmlFrac->pnext) {
		if(_pXmlFrac->type == FRAC_TYP_TXT) {
			sstrCat(&( _pXml->xml_string), ( _pXmlFrac->_str).phead);
		} else if( _pXmlFrac->type == FRAC_TYP_TAG && _pXmlFrac->status == FRAC_STA_REAL) {
			sstrCat(&( _pXml->xml_string), ( _pXmlFrac->_str).phead);
		}
	}

	return _pXml;
}


/**
 * @brief Read in the .xmlt file as template and parse it into a fraction list.
 *
 * @param _pTmplPath Path to XML template.
 * @param _pXmlFrac The head pointer of the fraction list.
 *
 * @return If _pXmlFrac is NULL, it will malloc a struct and return its pointer,
 * otherwise it will return _pXmlFrac.
 */
struct XmlFraction* readXmlTemplate(const char *_pTmplPath, struct XmlFraction *_pXmlFrac) {

	if(!api_isFileExist( _pTmplPath)) {
		return NULL;
	}
	if(_pXmlFrac == NULL) {
		_pXmlFrac = (struct XmlFraction *)MALLOC(sizeof(struct XmlFraction));
	}

	String tmpl = SSTR_INIT;
	
	FILE *pFile = fopen(_pTmplPath, "r");
	if(pFile == NULL)
		return NULL;

	readFile(pFile, &tmpl);

	return buildXmlFraction(tmpl.phead, _pXmlFrac);
	
}



#ifdef __cplusplus
}
#endif

#endif
